Skip to content

Support Ledger clear signing #1603

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 40 commits into
base: development
Choose a base branch
from

Conversation

franciszekjob
Copy link
Collaborator

@franciszekjob franciszekjob commented May 12, 2025

Closes #1473

Introduced changes

Add support for clear singing in LedgerSigner. Now we're getting displayed tx details before approving/rejecting it.

  • Add LedgerSigningMode
  • Use clear signing by default
clear_sign_example.mov

  • This PR contains breaking changes

Comment on lines -42 to -49
# Ledger constants
STARKNET_CLA = 0x5A
EIP_2645_PURPOSE = 0x80000A55
EIP_2645_PATH_LENGTH = 6
PUBLIC_KEY_RESPONSE_LENGTH = 65
SIGNATURE_RESPONSE_LENGTH = 65
VERSION_RESPONSE_LENGTH = 3

Copy link
Collaborator Author

@franciszekjob franciszekjob May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: These variables aren't used across multiple files so let's keep them in the dedicated (ledger) one.

Comment on lines -161 to +206
recipient_address = (
0x1323CACBC02B4AAED9BB6B24D121FB712D8946376040990F2F2FA0DCF17BB5B
)
recipient_address = 0x123
Copy link
Collaborator Author

@franciszekjob franciszekjob May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: 0x123 is better as an example address.

@@ -2,10 +2,13 @@
"version": 1,
"rules": [
{
"text": "Confirm Hash to sign",
"conditions": [],
"text": "Blind igning",
Copy link
Collaborator Author

@franciszekjob franciszekjob May 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: It's not a typo - for some reason that's how ledger displays the text
image

Comment on lines +17 to +46
@pytest.mark.parametrize(
"transaction",
[
InvokeV3(
version=3,
signature=[],
nonce=1,
resource_bounds=MAX_RESOURCE_BOUNDS_SEPOLIA,
calldata=[
1,
2009894490435840142178314390393166646092438090257831307886760648929397478285,
232670485425082704932579856502088130646006032362877466777181098476241604910,
3,
0x123,
100,
0,
],
sender_address=0x123,
),
DeployAccountV3(
class_hash=0x123,
contract_address_salt=0x123,
constructor_calldata=[1, 2, 3],
version=3,
signature=[],
nonce=0,
resource_bounds=MAX_RESOURCE_BOUNDS_SEPOLIA,
),
],
)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: We can't use mocks for clear singing as under the hood we're converting values from tx fields to bytes, and we would get TypeError: can't concat Mock to bytes.

@franciszekjob franciszekjob marked this pull request as ready for review May 13, 2025 08:05
@franciszekjob franciszekjob requested a review from ddoktorski May 13, 2025 08:52

def sign_transaction(self, transaction: AccountTransaction) -> List[int]:
if self.signing_mode == LedgerSigningMode.CLEAR:
if isinstance(transaction, DeclareV3):
raise ValueError("DeclareV3 signing is not supported bye LedgerSigner")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know why declare is not supported?

Copy link
Collaborator Author

@franciszekjob franciszekjob May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, haven't digged into the specific reason but maybe it's related with device hardware limits (considering that declare tx includes contract class) or security?

Comment on lines +463 to +471
if application_name == "LedgerW":
application_bytes = (
(43).to_bytes(1, byteorder="big")
+ (206).to_bytes(1, byteorder="big")
+ (231).to_bytes(1, byteorder="big")
+ (219).to_bytes(1, byteorder="big")
)
else:
application_bytes = _string_to_4byte_hash(application_name)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain this logic?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like these four integers are predefined for LedgerW and in other cases we just generate the representation.
I referred to https://github.com/starknet-io/starknet.js/blob/050616221a58545d79eece7040b4abc205c220e0/src/signer/ledgerSigner221.ts#L644

@franciszekjob franciszekjob requested a review from ddoktorski May 15, 2025 23:40
@franciszekjob franciszekjob requested a review from ddoktorski May 16, 2025 09:06
Comment on lines +298 to +299
if response is None:
raise ValueError("No response received from Ledger device.")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be checked inside the loop?

Copy link
Collaborator Author

@franciszekjob franciszekjob May 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No,apdu_exchange will always either raise an error or return response, so we mostly want to check if the loop was run.

response = self.app.client.apdu_exchange(
ins=3,
data=bytes(calldatas[0]),
p1=6,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

@franciszekjob franciszekjob May 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a typo - look that p1 is 5 here. Will add an issue in their repo for that.

if len(calldatas) > 1:
for part in calldatas[1:]:
response = self.app.client.apdu_exchange(
ins=3, p1=6, p2=1, data=part
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@franciszekjob franciszekjob requested a review from ddoktorski May 16, 2025 16:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Update LedgerSigner
2 participants